package org.litepal.crud;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

import org.litepal.LitePalBase;
import org.litepal.exceptions.DataSupportException;
import org.litepal.util.BaseUtility;

/**
 * This is the base class for CRUD component. All the common actions which can
 * be shared with each function in CURD component will be put here.
 * 
 * @author Tony Green
 * @since 1.1
 */
abstract class DataHandler extends LitePalBase {
	public static final String TAG = "DataHandler";

	/**
	 * Get the class name of baseObj.
	 * 
	 * @param baseObj
	 *            The DataSupport object to get class name.
	 * @return The class name of baseObj.
	 */
	protected String getBaseClassName(DataSupport baseObj) {
		return baseObj.getClass().getName();
	}

	/**
	 * It finds the getter method by the field. For example, field name is age,
	 * getter method name will be getAge. Then invoke the getter method and
	 * return the value.
	 * 
	 * @param dataSupport
	 *            The model to get method from.
	 * @param field
	 *            Use to generate getter method name.
	 * @return The value returned by getter method.
	 * @throws SecurityException
	 * @throws NoSuchMethodException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	protected Object takeGetMethodValueByField(DataSupport dataSupport, Field field)
			throws SecurityException, NoSuchMethodException, IllegalArgumentException,
			IllegalAccessException, InvocationTargetException {
		if (shouldGetOrSet(dataSupport, field)) {
			String getterMethodPrefix;
			if (isPrimitiveBooleanType(field)) {
				getterMethodPrefix = "is";
			} else {
				getterMethodPrefix = "get";
			}
			String getMethodName = getterMethodPrefix + BaseUtility.capitalize(field.getName());
			return DynamicExecutor.send(dataSupport, getMethodName, null, dataSupport.getClass(),
					null);
		}
		return null;
	}

	/**
	 * It finds the setter method by the field. For example, field name is age,
	 * setter method name will be setAge. Then invoke the setter method with
	 * necessary parameter.
	 * 
	 * @param dataSupport
	 *            The model to set method to.
	 * @param field
	 *            Use to generate setter method name.
	 * @param parameter
	 *            The parameter to invoke setter method.
	 * @throws SecurityException
	 * @throws NoSuchMethodException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	protected void putSetMethodValueByField(DataSupport dataSupport, Field field, Object parameter)
			throws SecurityException, NoSuchMethodException, IllegalArgumentException,
			IllegalAccessException, InvocationTargetException {
		if (shouldGetOrSet(dataSupport, field)) {
			String setterMethodPrefix = "set";
			String setMethodName = setterMethodPrefix + BaseUtility.capitalize(field.getName());
			DynamicExecutor.send(dataSupport, setMethodName, new Object[] { parameter },
					dataSupport.getClass(), new Class[] { field.getType() });
		}
	}

	// /**
	// * Each model holds a collection of associated models once it inherits
	// from
	// * DataSuport. This method helps add a associatedModel into associated
	// * models collection of baseObj.
	// *
	// * @param baseObj
	// * The model to add associated model.
	// * @param associatedModel
	// * The model to be added into associated model collection.
	// * @param isForeignKeyColumn
	// * This model has a foreign key column of the associated model or
	// * not.
	// * @throws SecurityException
	// * @throws NoSuchMethodException
	// * @throws IllegalArgumentException
	// * @throws IllegalAccessException
	// * @throws InvocationTargetException
	// */
	// protected void addAssociatedModel(DataSupport baseObj, DataSupport
	// associatedModel,
	// boolean isForeignKeyColumn) throws SecurityException,
	// NoSuchMethodException,
	// IllegalArgumentException, IllegalAccessException,
	// InvocationTargetException {
	// DynamicExecutor.send(baseObj, "addAssociatedModel", new Object[] {
	// associatedModel,
	// isForeignKeyColumn }, DataSupport.class, new Class[] { DataSupport.class,
	// boolean.class });
	// }
	//
	// /**
	// * Each model holds a collection of associated models once it inherits
	// from
	// * DataSuport. This method helps get the associated models from baseObj.
	// *
	// * @param baseObj
	// * The model to get associated models from.
	// * @return A set holds all the associated models of baseObj.
	// * @throws SecurityException
	// * @throws NoSuchMethodException
	// * @throws IllegalArgumentException
	// * @throws IllegalAccessException
	// * @throws InvocationTargetException
	// */
	// @SuppressWarnings("unchecked")
	// protected Set<DataSupport> getAssociatedModels(DataSupport baseObj)
	// throws SecurityException,
	// NoSuchMethodException, IllegalArgumentException, IllegalAccessException,
	// InvocationTargetException {
	// return (Set<DataSupport>) DynamicExecutor.send(baseObj,
	// "getAssociatedModels", null,
	// DataSupport.class, null);
	// }

	/**
	 * Judge the baseObj has a foreign key column of the associated model or
	 * not.
	 * 
	 * @param baseObj
	 *            The base model to judge from.
	 * @param associatedModel
	 *            The associated model for baseObj.
	 * @return True if baseObj has a foreign key column of associatedModel,
	 *         false otherwise.
	 * @throws SecurityException
	 * @throws IllegalArgumentException
	 * @throws NoSuchMethodException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	protected boolean hasForeignKeyColumn(DataSupport baseObj, DataSupport associatedModel)
			throws SecurityException, IllegalArgumentException, NoSuchMethodException,
			IllegalAccessException, InvocationTargetException {
		return baseObj.hasForeignKeyColumn(associatedModel);
	}

	/**
	 * Do not suggest use this method to find DataSupport class from hierarchy.
	 * Try to use DataSupport.class directly.
	 * 
	 * Detect the baseObj is an instance of DataSupport or not. If true, return
	 * the class of DataSupport. Otherwise throw an exception of
	 * DataSupportException to tell user baseObj is not an instance of
	 * DataSupport.
	 * 
	 * @param baseObj
	 *            The base object model.
	 * @return The class of DataSupport or throw DataSupportException.
	 * @throws DataSupportException
	 */
	@Deprecated
	protected Class<?> findDataSupportClass(DataSupport baseObj) {
		Class<?> superClass = null;
		while (true) {
			superClass = baseObj.getClass().getSuperclass();
			if (superClass == null || DataSupport.class == superClass) {
				break;
			}
		}
		if (superClass == null) {
			throw new DataSupportException(baseObj.getClass().getName()
					+ DataSupportException.MODEL_IS_NOT_AN_INSTANCE_OF_DATA_SUPPORT);
		}
		return superClass;
	}

	/**
	 * Judge a field is a primitive boolean type or not. Cause there's something
	 * special when use eclipse to generate getter method. The primitive boolean
	 * type won't be like <b>getXxx</b>, it's something like <b>isXxx</b>.
	 * 
	 * @param field
	 *            Use field to get field type.
	 * @return If it's primitive boolean type return true, else return false.
	 */
	private boolean isPrimitiveBooleanType(Field field) {
		Class<?> fieldType = field.getType();
		if ("boolean".equals(fieldType.getName())) {
			return true;
		}
		return false;
	}

	/**
	 * When executing {@link #takeGetMethodValueByField(DataSupport, Field)} or
	 * {@link #putSetMethodValueByField(DataSupport, Field, Object)}, the
	 * dataSupport and field passed in should be protected from null value.
	 * 
	 * @param dataSupport
	 *            The object to execute set or get method.
	 * @param field
	 *            The field of generating set and get methods.
	 * @return True if dataSupport and field are not null, false otherwise.
	 */
	private boolean shouldGetOrSet(DataSupport dataSupport, Field field) {
		return dataSupport != null && field != null;
	}
}
